#include <memory>
#include <vector>

#include <logger.h>

class MemoryObject {
public:
    virtual ~MemoryObject() = default;
    virtual int GetMemoryUsage() const
    {
        LOG2SO(DEBUG) << "call MemoryObject" << number;
        return sizeof(*this);
    }

private:
    int number = 0;
};

class Object2 : public MemoryObject {
public:
    int GetMemoryUsage() const override
    {
        LOG2SO(DEBUG) << "call Object2" << number2;
        return sizeof(*this);
    }

private:
    double number2 = 0;
};

template<class T>
int GetMemoryUsage(const T &obj)
{
    LOG2SO(DEBUG) << "T";
    return sizeof(obj);
}

int GetMemoryUsage(const MemoryObject &t)
{
    LOG2SO(DEBUG) << "MemoryObject";
    return t.GetMemoryUsage();
}

template<class T>
int GetMemoryUsage(const std::vector<T> &objs)
{
    LOG2SO(DEBUG) << "std::vector<T>";
    if (objs.capacity() == 0) {
        return 24;
    }

    auto ret = 40 + sizeof(T) * (objs.capacity() - objs.size());
    for (const auto &obj : objs) {
        if (std::is_base_of_v<MemoryObject, T>) {
            ret += GetMemoryUsage((MemoryObject &)obj);
        } else {
            ret += GetMemoryUsage(obj);
        }
    }

    return ret;
}

template<class T>
int GetMemoryUsage(const std::shared_ptr<T> &obj)
{
    LOG2SO(DEBUG) << "std::shared_ptr<T>";
    if (obj == nullptr) {
        return sizeof(obj);
    }

    if (std::is_base_of_v<MemoryObject, T>) {
        return sizeof(obj) + GetMemoryUsage((MemoryObject &)*obj) / obj.use_count();
    }

    return sizeof(obj) + sizeof(T) / obj.use_count();
}

int main()
{
    std::vector<int> ints = {{}, {}, {}};
    VALUE_LOGGER(GetMemoryUsage(ints));
    // std::vector<T>
    // T
    // T
    // T
    // GetMemoryUsage(ints): '52'

    std::vector<MemoryObject> objs = {{}, {}, {}};
    VALUE_LOGGER(GetMemoryUsage(objs));
    // std::vector<T>
    // MemoryObject
    // call MemoryObject
    // MemoryObject
    // call MemoryObject
    // MemoryObject
    // call MemoryObject
    // GetMemoryUsage(objs): '88'

    std::vector<Object2> obj2s = {{}, {}, {}};
    VALUE_LOGGER(GetMemoryUsage(obj2s));
    // std::vector<T>
    // MemoryObject
    // call Object2
    // MemoryObject
    // call Object2
    // MemoryObject
    // call Object2
    // GetMemoryUsage(obj2s): '112'

    std::vector<std::shared_ptr<MemoryObject>> pobjs = {};
    pobjs.push_back(nullptr);
    pobjs.push_back(std::make_shared<MemoryObject>());
    pobjs.push_back(nullptr);
    VALUE_LOGGER(GetMemoryUsage(pobjs));
    // std::vector<T>
    // std::shared_ptr<T>
    // std::shared_ptr<T>
    // MemoryObject
    // call MemoryObject
    // std::shared_ptr<T>
    // GetMemoryUsage(pobjs): '120'
    return 0;
}
